/*
 * This file is part of "JTA - Telnet/SSH for the JAVA(tm) platform".
 *
 * (c) Matthias L. Jugel, Marcus Meiner 1996-2005. All Rights Reserved.
 *
 * Please visit http://javatelnet.org/ for updates and contact.
 *
 * --LICENSE NOTICE--
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 * --LICENSE NOTICE--
 *
 */

package de.mud.terminal;

import java.util.Properties;

/**
 * Implementation of a VT terminal emulation plus ANSI compatible.
 * <P>
 * <B>Maintainer:</B> Marcus Meißner
 * 
 * @version $Id: vt320.java,v 1.4 2010/03/23 04:07:29 zhang42 Exp $
 * @author Matthias L. Jugel, Marcus Meißner
 */
public abstract class vt320 extends VDUBuffer implements VDUInput {

	/**
	 * The current version id tag.
	 * <P>
	 * $Id: vt320.java,v 1.4 2010/03/23 04:07:29 zhang42 Exp $
	 * 
	 */
	public final static String ID = "$Id: vt320.java,v 1.4 2010/03/23 04:07:29 zhang42 Exp $";

	/** the debug level */
	private final static int debug = 0;
	private StringBuilder debugStr;

	public abstract void debug(String notice);

	/**
	 * Write an answer back to the remote host. This is needed to be able to
	 * send terminal answers requests like status and type information.
	 * 
	 * @param b
	 *            the array of bytes to be sent
	 */
	public abstract void write(byte[] b);

	/**
	 * Write an answer back to the remote host. This is needed to be able to
	 * send terminal answers requests like status and type information.
	 * 
	 * @param b
	 *            the array of bytes to be sent
	 */
	public abstract void write(int b);

	/**
	 * Play the beep sound ...
	 */
	public void beep() { /* do nothing by default */
	}

	/**
	 * Convenience function for putString(char[], int, int)
	 */
	public void putString(String s) {
		int len = s.length();
		char[] tmp = new char[len];
		s.getChars(0, len, tmp, 0);
		putString(tmp, null, 0, len);
	}

	/**
	 * Put string at current cursor position. Moves cursor according to the
	 * String. Does NOT wrap.
	 * 
	 * @param s
	 *            character array
	 * @param start
	 *            place to start in array
	 * @param len
	 *            number of characters to process
	 */
	public void putString(char[] s, byte[] fullwidths, int start, int len) {
		if (len > 0) {
			// markLine(R, 1);
			int lastChar = -1;
			char c;
			boolean isWide = false;

			for (int i = 0; i < len; i++) {
				c = s[start + i];
				// Shortcut for my favorite ASCII
				if (c <= 0x7F) {
					if (lastChar != -1)
						putChar((char) lastChar, isWide, false);
					lastChar = c;
					isWide = false;
				} else if (!Character.isLowSurrogate(c)
						&& !Character.isHighSurrogate(c)) {
					if (Character.getType(c) == Character.NON_SPACING_MARK) {
						if (lastChar != -1) {
							char nc = Precomposer
									.precompose((char) lastChar, c);
							putChar(nc, isWide, false);
							lastChar = -1;
						}
					} else {
						if (lastChar != -1)
							putChar((char) lastChar, isWide, false);
						lastChar = c;
						if (fullwidths != null)
							isWide = fullwidths[i] == 1;
					}
				}
			}

			if (lastChar != -1)
				putChar((char) lastChar, isWide, false);

			setCursorPosition(C, R);
			redraw();
		}
	}

	protected void sendTelnetCommand(byte cmd) {

	}

	/**
	 * Sent the changed window size from the terminal to all listeners.
	 */
	protected void setWindowSize(int c, int r) {
		/* To be overridden by Terminal.java */
	}

	@Override
	public void setScreenSize(int c, int r, boolean broadcast) {
		if (debug > 2) {
			if (debugStr == null)
				debugStr = new StringBuilder();

			debugStr.append("setscreensize (").append(c).append(',').append(r)
					.append(',').append(broadcast).append(')');
			debug(debugStr.toString());
			debugStr.setLength(0);
		}

		super.setScreenSize(c, r, false);

		boolean cursorChanged = false;

		// Don't let the cursor go off the screen.
		if (C >= c) {
			C = c - 1;
			cursorChanged = true;
		}

		if (R >= r) {
			R = r - 1;
			cursorChanged = true;
		}

		if (cursorChanged) {
			setCursorPosition(C, R);
			redraw();
		}

		if (broadcast) {
			setWindowSize(c, r); /* broadcast up */
		}
	}

	/**
	 * Create a new vt320 terminal and intialize it with useful settings.
	 */
	public vt320(int width, int height) {
		super(width, height);

		debugStr = new StringBuilder();

		setVMS(false);
		setIBMCharset(false);
		setTerminalID("vt320");
		setBufferSize(100);
		// setBorder(2, false);

		gx = new char[4];
		reset();

		/* top row of numpad */
		PF1 = "\u001bOP";
		PF2 = "\u001bOQ";
		PF3 = "\u001bOR";
		PF4 = "\u001bOS";

		/* the 3x2 keyblock on PC keyboards */
		Insert = new String[4];
		Remove = new String[4];
		KeyHome = new String[4];
		KeyEnd = new String[4];
		NextScn = new String[4];
		PrevScn = new String[4];
		Escape = new String[4];
		BackSpace = new String[4];
		TabKey = new String[4];
		Insert[0] = Insert[1] = Insert[2] = Insert[3] = "\u001b[2~";
		Remove[0] = Remove[1] = Remove[2] = Remove[3] = "\u001b[3~";
		PrevScn[0] = PrevScn[1] = PrevScn[2] = PrevScn[3] = "\u001b[5~";
		NextScn[0] = NextScn[1] = NextScn[2] = NextScn[3] = "\u001b[6~";
		KeyHome[0] = KeyHome[1] = KeyHome[2] = KeyHome[3] = "\u001b[H";
		KeyEnd[0] = KeyEnd[1] = KeyEnd[2] = KeyEnd[3] = "\u001b[F";
		Escape[0] = Escape[1] = Escape[2] = Escape[3] = "\u001b";
		if (vms) {
			BackSpace[1] = "" + (char) 10; // VMS shift deletes word back
			BackSpace[2] = "\u0018"; // VMS control deletes line back
			BackSpace[0] = BackSpace[3] = "\u007f"; // VMS other is delete
		} else {
			// BackSpace[0] = BackSpace[1] = BackSpace[2] = BackSpace[3] = "\b";
			// ConnectBot modifications.
			BackSpace[0] = "\b";
			BackSpace[1] = "\u007f";
			BackSpace[2] = "\u001b[3~";
			BackSpace[3] = "\u001b[2~";
		}

		/* some more VT100 keys */
		Find = "\u001b[1~";
		Select = "\u001b[4~";
		Help = "\u001b[28~";
		Do = "\u001b[29~";

		FunctionKey = new String[21];
		FunctionKey[0] = "";
		FunctionKey[1] = PF1;
		FunctionKey[2] = PF2;
		FunctionKey[3] = PF3;
		FunctionKey[4] = PF4;
		/* following are defined differently for vt220 / vt132 ... */
		FunctionKey[5] = "\u001b[15~";
		FunctionKey[6] = "\u001b[17~";
		FunctionKey[7] = "\u001b[18~";
		FunctionKey[8] = "\u001b[19~";
		FunctionKey[9] = "\u001b[20~";
		FunctionKey[10] = "\u001b[21~";
		FunctionKey[11] = "\u001b[23~";
		FunctionKey[12] = "\u001b[24~";
		FunctionKey[13] = "\u001b[25~";
		FunctionKey[14] = "\u001b[26~";
		FunctionKey[15] = Help;
		FunctionKey[16] = Do;
		FunctionKey[17] = "\u001b[31~";
		FunctionKey[18] = "\u001b[32~";
		FunctionKey[19] = "\u001b[33~";
		FunctionKey[20] = "\u001b[34~";

		FunctionKeyShift = new String[21];
		FunctionKeyAlt = new String[21];
		FunctionKeyCtrl = new String[21];

		for (int i = 0; i < 20; i++) {
			FunctionKeyShift[i] = "";
			FunctionKeyAlt[i] = "";
			FunctionKeyCtrl[i] = "";
		}
		FunctionKeyShift[15] = Find;
		FunctionKeyShift[16] = Select;

		TabKey[0] = "\u0009";
		TabKey[1] = "\u001bOP\u0009";
		TabKey[2] = TabKey[3] = "";

		KeyUp = new String[4];
		KeyUp[0] = "\u001b[A";
		KeyDown = new String[4];
		KeyDown[0] = "\u001b[B";
		KeyRight = new String[4];
		KeyRight[0] = "\u001b[C";
		KeyLeft = new String[4];
		KeyLeft[0] = "\u001b[D";
		Numpad = new String[10];
		Numpad[0] = "\u001bOp";
		Numpad[1] = "\u001bOq";
		Numpad[2] = "\u001bOr";
		Numpad[3] = "\u001bOs";
		Numpad[4] = "\u001bOt";
		Numpad[5] = "\u001bOu";
		Numpad[6] = "\u001bOv";
		Numpad[7] = "\u001bOw";
		Numpad[8] = "\u001bOx";
		Numpad[9] = "\u001bOy";
		KPMinus = PF4;
		KPComma = "\u001bOl";
		KPPeriod = "\u001bOn";
		KPEnter = "\u001bOM";

		NUMPlus = new String[4];
		NUMPlus[0] = "+";
		NUMDot = new String[4];
		NUMDot[0] = ".";
	}

	public void setBackspace(int type) {
		switch (type) {
		case DELETE_IS_DEL:
			BackSpace[0] = "\u007f";
			BackSpace[1] = "\b";
			break;
		case DELETE_IS_BACKSPACE:
			BackSpace[0] = "\b";
			BackSpace[1] = "\u007f";
			break;
		}
	}

	/**
	 * Create a default vt320 terminal with 80 columns and 24 lines.
	 */
	public vt320() {
		this(80, 24);
	}

	/**
	 * Terminal is mouse-aware and requires (x,y) coordinates of on the terminal
	 * (character coordinates) and the button clicked.
	 * 
	 * @param x
	 * @param y
	 * @param modifiers
	 */
	public void mousePressed(int x, int y, int modifiers) {
		if (mouserpt == 0)
			return;

		int mods = modifiers;
		mousebut = 3;
		if ((mods & 16) == 16)
			mousebut = 0;
		if ((mods & 8) == 8)
			mousebut = 1;
		if ((mods & 4) == 4)
			mousebut = 2;

		int mousecode;
		if (mouserpt == 9) /* X10 Mouse */
			mousecode = 0x20 | mousebut;
		else
			/* normal xterm mouse reporting */
			mousecode = mousebut | 0x20 | ((mods & 7) << 2);

		byte b[] = new byte[6];

		b[0] = 27;
		b[1] = (byte) '[';
		b[2] = (byte) 'M';
		b[3] = (byte) mousecode;
		b[4] = (byte) (0x20 + x + 1);
		b[5] = (byte) (0x20 + y + 1);

		write(b); // FIXME: writeSpecial here
	}

	/**
	 * Terminal is mouse-aware and requires the coordinates and button of the
	 * release.
	 * 
	 * @param x
	 * @param y
	 * @param modifiers
	 */
	public void mouseReleased(int x, int y, int modifiers) {
		if (mouserpt == 0)
			return;

		/*
		 * problem is tht modifiers still have the released button set in them.
		 * int mods = modifiers; mousebut = 3; if ((mods & 16)==16) mousebut=0;
		 * if ((mods & 8)==8 ) mousebut=1; if ((mods & 4)==4 ) mousebut=2;
		 */

		int mousecode;
		if (mouserpt == 9)
			mousecode = 0x20 + mousebut; /* same as press? appears so. */
		else
			mousecode = '#';

		byte b[] = new byte[6];
		b[0] = 27;
		b[1] = (byte) '[';
		b[2] = (byte) 'M';
		b[3] = (byte) mousecode;
		b[4] = (byte) (0x20 + x + 1);
		b[5] = (byte) (0x20 + y + 1);
		write(b); // FIXME: writeSpecial here
		mousebut = 0;
	}

	/** we should do localecho (passed from other modules). false is default */
	private boolean localecho = false;

	/**
	 * Enable or disable the local echo property of the terminal.
	 * 
	 * @param echo
	 *            true if the terminal should echo locally
	 */
	public void setLocalEcho(boolean echo) {
		localecho = echo;
	}

	/**
	 * Enable the VMS mode of the terminal to handle some things differently for
	 * VMS hosts.
	 * 
	 * @param vms
	 *            true for vms mode, false for normal mode
	 */
	public void setVMS(boolean vms) {
		this.vms = vms;
	}

	/**
	 * Enable the usage of the IBM character set used by some BBS's. Special
	 * graphical character are available in this mode.
	 * 
	 * @param ibm
	 *            true to use the ibm character set
	 */
	public void setIBMCharset(boolean ibm) {
		useibmcharset = ibm;
	}

	/**
	 * Override the standard key codes used by the terminal emulation.
	 * 
	 * @param codes
	 *            a properties object containing key code definitions
	 */
	public void setKeyCodes(Properties codes) {
		String res, prefixes[] = { "", "S", "C", "A" };
		int i;

		for (i = 0; i < 10; i++) {
			res = codes.getProperty("NUMPAD" + i);
			if (res != null)
				Numpad[i] = unEscape(res);
		}
		for (i = 1; i < 20; i++) {
			res = codes.getProperty("F" + i);
			if (res != null)
				FunctionKey[i] = unEscape(res);
			res = codes.getProperty("SF" + i);
			if (res != null)
				FunctionKeyShift[i] = unEscape(res);
			res = codes.getProperty("CF" + i);
			if (res != null)
				FunctionKeyCtrl[i] = unEscape(res);
			res = codes.getProperty("AF" + i);
			if (res != null)
				FunctionKeyAlt[i] = unEscape(res);
		}
		for (i = 0; i < 4; i++) {
			res = codes.getProperty(prefixes[i] + "PGUP");
			if (res != null)
				PrevScn[i] = unEscape(res);
			res = codes.getProperty(prefixes[i] + "PGDOWN");
			if (res != null)
				NextScn[i] = unEscape(res);
			res = codes.getProperty(prefixes[i] + "END");
			if (res != null)
				KeyEnd[i] = unEscape(res);
			res = codes.getProperty(prefixes[i] + "HOME");
			if (res != null)
				KeyHome[i] = unEscape(res);
			res = codes.getProperty(prefixes[i] + "INSERT");
			if (res != null)
				Insert[i] = unEscape(res);
			res = codes.getProperty(prefixes[i] + "REMOVE");
			if (res != null)
				Remove[i] = unEscape(res);
			res = codes.getProperty(prefixes[i] + "UP");
			if (res != null)
				KeyUp[i] = unEscape(res);
			res = codes.getProperty(prefixes[i] + "DOWN");
			if (res != null)
				KeyDown[i] = unEscape(res);
			res = codes.getProperty(prefixes[i] + "LEFT");
			if (res != null)
				KeyLeft[i] = unEscape(res);
			res = codes.getProperty(prefixes[i] + "RIGHT");
			if (res != null)
				KeyRight[i] = unEscape(res);
			res = codes.getProperty(prefixes[i] + "ESCAPE");
			if (res != null)
				Escape[i] = unEscape(res);
			res = codes.getProperty(prefixes[i] + "BACKSPACE");
			if (res != null)
				BackSpace[i] = unEscape(res);
			res = codes.getProperty(prefixes[i] + "TAB");
			if (res != null)
				TabKey[i] = unEscape(res);
			res = codes.getProperty(prefixes[i] + "NUMPLUS");
			if (res != null)
				NUMPlus[i] = unEscape(res);
			res = codes.getProperty(prefixes[i] + "NUMDECIMAL");
			if (res != null)
				NUMDot[i] = unEscape(res);
		}
	}

	/**
	 * Set the terminal id used to identify this terminal.
	 * 
	 * @param terminalID
	 *            the id string
	 */
	public void setTerminalID(String terminalID) {
		this.terminalID = terminalID;

		if (terminalID.equals("scoansi")) {
			FunctionKey[1] = "\u001b[M";
			FunctionKey[2] = "\u001b[N";
			FunctionKey[3] = "\u001b[O";
			FunctionKey[4] = "\u001b[P";
			FunctionKey[5] = "\u001b[Q";
			FunctionKey[6] = "\u001b[R";
			FunctionKey[7] = "\u001b[S";
			FunctionKey[8] = "\u001b[T";
			FunctionKey[9] = "\u001b[U";
			FunctionKey[10] = "\u001b[V";
			FunctionKey[11] = "\u001b[W";
			FunctionKey[12] = "\u001b[X";
			FunctionKey[13] = "\u001b[Y";
			FunctionKey[14] = "?";
			FunctionKey[15] = "\u001b[a";
			FunctionKey[16] = "\u001b[b";
			FunctionKey[17] = "\u001b[c";
			FunctionKey[18] = "\u001b[d";
			FunctionKey[19] = "\u001b[e";
			FunctionKey[20] = "\u001b[f";
			PrevScn[0] = PrevScn[1] = PrevScn[2] = PrevScn[3] = "\u001b[I";
			NextScn[0] = NextScn[1] = NextScn[2] = NextScn[3] = "\u001b[G";
			// more theoretically.
		}
	}

	public void setAnswerBack(String ab) {
		this.answerBack = unEscape(ab);
	}

	/**
	 * Get the terminal id used to identify this terminal.
	 */
	public String getTerminalID() {
		return terminalID;
	}

	/**
	 * A small conveniance method thar converts the string to a byte array for
	 * sending.
	 * 
	 * @param s
	 *            the string to be sent
	 */
	private boolean write(String s, boolean doecho) {
		if (debug > 2) {
			debugStr.append("write(|").append(s).append("|,").append(doecho);
			debug(debugStr.toString());
			debugStr.setLength(0);
		}
		if (s == null) // aka the empty string.
			return true;
		/*
		 * NOTE: getBytes() honours some locale, it *CONVERTS* the string.
		 * However, we output only 7bit stuff towards the target, and *some* 8
		 * bit control codes. We must not mess up the latter, so we do hand by
		 * hand copy.
		 */

		byte arr[] = new byte[s.length()];
		for (int i = 0; i < s.length(); i++) {
			arr[i] = (byte) s.charAt(i);
		}
		write(arr);

		if (doecho)
			putString(s);
		return true;
	}

	private boolean write(int s, boolean doecho) {
		if (debug > 2) {
			debugStr.append("write(|").append(s).append("|,").append(doecho);
			debug(debugStr.toString());
			debugStr.setLength(0);
		}

		write(s);

		// TODO check if character is wide
		if (doecho)
			putChar((char) s, false, false);
		return true;
	}

	private boolean write(String s) {
		return write(s, localecho);
	}

	// ===================================================================
	// the actual terminal emulation code comes here:
	// ===================================================================

	private String terminalID = "vt320";
	private String answerBack = "Use Terminal.answerback to set ...\n";

	// X - COLUMNS, Y - ROWS
	int R, C;
	int attributes = 0;

	int Sc, Sr, Sa, Stm, Sbm;
	char Sgr, Sgl;
	char Sgx[];

	int insertmode = 0;
	int statusmode = 0;
	boolean vt52mode = false;
	boolean keypadmode = false; /* false - numeric, true - application */
	boolean output8bit = false;
	int normalcursor = 0;
	boolean moveoutsidemargins = true;
	boolean wraparound = true;
	boolean sendcrlf = true;
	boolean capslock = false;
	boolean numlock = false;
	int mouserpt = 0;
	byte mousebut = 0;

	boolean useibmcharset = false;

	int lastwaslf = 0;
	boolean usedcharsets = false;

	private final static char ESC = 27;
	private final static char IND = 132;
	private final static char NEL = 133;
	private final static char RI = 141;
	private final static char SS2 = 142;
	private final static char SS3 = 143;
	private final static char DCS = 144;
	private final static char HTS = 136;
	private final static char CSI = 155;
	private final static char OSC = 157;
	private final static int TSTATE_DATA = 0;
	private final static int TSTATE_ESC = 1; /* ESC */
	private final static int TSTATE_CSI = 2; /* ESC [ */
	private final static int TSTATE_DCS = 3; /* ESC P */
	private final static int TSTATE_DCEQ = 4; /* ESC [? */
	private final static int TSTATE_ESCSQUARE = 5; /* ESC # */
	private final static int TSTATE_OSC = 6; /* ESC ] */
	private final static int TSTATE_SETG0 = 7; /* ESC (? */
	private final static int TSTATE_SETG1 = 8; /* ESC )? */
	private final static int TSTATE_SETG2 = 9; /* ESC *? */
	private final static int TSTATE_SETG3 = 10; /* ESC +? */
	private final static int TSTATE_CSI_DOLLAR = 11; /* ESC [ Pn $ */
	private final static int TSTATE_CSI_EX = 12; /* ESC [ ! */
	private final static int TSTATE_ESCSPACE = 13; /* ESC <space> */
	private final static int TSTATE_VT52X = 14;
	private final static int TSTATE_VT52Y = 15;
	private final static int TSTATE_CSI_TICKS = 16;
	private final static int TSTATE_CSI_EQUAL = 17; /* ESC [ = */
	private final static int TSTATE_TITLE = 18; /* xterm title */

	/* Keys we support */
	public final static int KEY_PAUSE = 1;
	public final static int KEY_F1 = 2;
	public final static int KEY_F2 = 3;
	public final static int KEY_F3 = 4;
	public final static int KEY_F4 = 5;
	public final static int KEY_F5 = 6;
	public final static int KEY_F6 = 7;
	public final static int KEY_F7 = 8;
	public final static int KEY_F8 = 9;
	public final static int KEY_F9 = 10;
	public final static int KEY_F10 = 11;
	public final static int KEY_F11 = 12;
	public final static int KEY_F12 = 13;
	public final static int KEY_UP = 14;
	public final static int KEY_DOWN = 15;
	public final static int KEY_LEFT = 16;
	public final static int KEY_RIGHT = 17;
	public final static int KEY_PAGE_DOWN = 18;
	public final static int KEY_PAGE_UP = 19;
	public final static int KEY_INSERT = 20;
	public final static int KEY_DELETE = 21;
	public final static int KEY_BACK_SPACE = 22;
	public final static int KEY_HOME = 23;
	public final static int KEY_END = 24;
	public final static int KEY_NUM_LOCK = 25;
	public final static int KEY_CAPS_LOCK = 26;
	public final static int KEY_SHIFT = 27;
	public final static int KEY_CONTROL = 28;
	public final static int KEY_ALT = 29;
	public final static int KEY_ENTER = 30;
	public final static int KEY_NUMPAD0 = 31;
	public final static int KEY_NUMPAD1 = 32;
	public final static int KEY_NUMPAD2 = 33;
	public final static int KEY_NUMPAD3 = 34;
	public final static int KEY_NUMPAD4 = 35;
	public final static int KEY_NUMPAD5 = 36;
	public final static int KEY_NUMPAD6 = 37;
	public final static int KEY_NUMPAD7 = 38;
	public final static int KEY_NUMPAD8 = 39;
	public final static int KEY_NUMPAD9 = 40;
	public final static int KEY_DECIMAL = 41;
	public final static int KEY_ADD = 42;
	public final static int KEY_ESCAPE = 43;

	public final static int DELETE_IS_DEL = 0;
	public final static int DELETE_IS_BACKSPACE = 1;

	/*
	 * The graphics charsets B - default ASCII A - ISO Latin 1 0 - DEC SPECIAL <
	 * - User defined ....
	 */
	char gx[];
	char gl; // GL (left charset)
	char gr; // GR (right charset)
	int onegl; // single shift override for GL.

	// Map from scoansi linedrawing to DEC _and_ unicode (for the stuff which
	// is not in linedrawing). Got from experimenting with scoadmin.
	private final static String scoansi_acs = "Tm7k3x4u?kZl@mYjEnB\u2566DqCtAvM\u2550:\u2551N\u2557I\u2554;\u2557H\u255a0a<\u255d";
	// array to store DEC Special -> Unicode mapping
	// Unicode DEC Unicode name (DEC name)
	private static char DECSPECIAL[] = { '\u0040', // 5f blank
			'\u2666', // 60 black diamond
			'\u2592', // 61 grey square
			'\u2409', // 62 Horizontal tab (ht) pict. for control
			'\u240c', // 63 Form Feed (ff) pict. for control
			'\u240d', // 64 Carriage Return (cr) pict. for control
			'\u240a', // 65 Line Feed (lf) pict. for control
			'\u00ba', // 66 Masculine ordinal indicator
			'\u00b1', // 67 Plus or minus sign
			'\u2424', // 68 New Line (nl) pict. for control
			'\u240b', // 69 Vertical Tab (vt) pict. for control
			'\u2518', // 6a Forms light up and left
			'\u2510', // 6b Forms light down and left
			'\u250c', // 6c Forms light down and right
			'\u2514', // 6d Forms light up and right
			'\u253c', // 6e Forms light vertical and horizontal
			'\u2594', // 6f Upper 1/8 block (Scan 1)
			'\u2580', // 70 Upper 1/2 block (Scan 3)
			'\u2500', // 71 Forms light horizontal or ?em dash? (Scan 5)
			'\u25ac', // 72 \u25ac black rect. or \u2582 lower 1/4 (Scan 7)
			'\u005f', // 73 \u005f underscore or \u2581 lower 1/8 (Scan 9)
			'\u251c', // 74 Forms light vertical and right
			'\u2524', // 75 Forms light vertical and left
			'\u2534', // 76 Forms light up and horizontal
			'\u252c', // 77 Forms light down and horizontal
			'\u2502', // 78 vertical bar
			'\u2264', // 79 less than or equal
			'\u2265', // 7a greater than or equal
			'\u00b6', // 7b paragraph
			'\u2260', // 7c not equal
			'\u00a3', // 7d Pound Sign (british)
			'\u00b7' // 7e Middle Dot
	};

	/** Strings to send on function key pressing */
	private String Numpad[];
	private String FunctionKey[];
	private String FunctionKeyShift[];
	private String FunctionKeyCtrl[];
	private String FunctionKeyAlt[];
	private String TabKey[];
	private String KeyUp[], KeyDown[], KeyLeft[], KeyRight[];
	private String KPMinus, KPComma, KPPeriod, KPEnter;
	private String PF1, PF2, PF3, PF4;
	private String Help, Do, Find, Select;

	private String KeyHome[], KeyEnd[], Insert[], Remove[], PrevScn[],
			NextScn[];
	private String Escape[], BackSpace[], NUMDot[], NUMPlus[];

	private String osc, dcs; /* to memorize OSC & DCS control sequence */

	/** vt320 state variable (internal) */
	private int term_state = TSTATE_DATA;
	/** in vms mode, set by Terminal.VMS property */
	private boolean vms = false;
	/** Tabulators */
	private byte[] Tabs;
	/** The list of integers as used by CSI */
	private int[] DCEvars = new int[30];
	private int DCEvar;

	/**
	 * Replace escape code characters (backslash + identifier) with their
	 * respective codes.
	 * 
	 * @param tmp
	 *            the string to be parsed
	 * @return a unescaped string
	 */
	static String unEscape(String tmp) {
		int idx = 0, oldidx = 0;
		String cmd;
		// f.println("unescape("+tmp+")");
		cmd = "";
		while ((idx = tmp.indexOf('\\', oldidx)) >= 0 && ++idx <= tmp.length()) {
			cmd += tmp.substring(oldidx, idx - 1);
			if (idx == tmp.length())
				return cmd;
			switch (tmp.charAt(idx)) {
			case 'b':
				cmd += "\b";
				break;
			case 'e':
				cmd += "\u001b";
				break;
			case 'n':
				cmd += "\n";
				break;
			case 'r':
				cmd += "\r";
				break;
			case 't':
				cmd += "\t";
				break;
			case 'v':
				cmd += "\u000b";
				break;
			case 'a':
				cmd += "\u0012";
				break;
			default:
				if ((tmp.charAt(idx) >= '0') && (tmp.charAt(idx) <= '9')) {
					int i;
					for (i = idx; i < tmp.length(); i++)
						if ((tmp.charAt(i) < '0') || (tmp.charAt(i) > '9'))
							break;
					cmd += (char) Integer.parseInt(tmp.substring(idx, i));
					idx = i - 1;
				} else
					cmd += tmp.substring(idx, ++idx);
				break;
			}
			oldidx = ++idx;
		}
		if (oldidx <= tmp.length())
			cmd += tmp.substring(oldidx);
		return cmd;
	}

	/**
	 * A small conveniance method thar converts a 7bit string to the 8bit
	 * version depending on VT52/Output8Bit mode.
	 * 
	 * @param s
	 *            the string to be sent
	 */
	private boolean writeSpecial(String s) {
		if (s == null)
			return true;
		if (((s.length() >= 3) && (s.charAt(0) == 27) && (s.charAt(1) == 'O'))) {
			if (vt52mode) {
				if ((s.charAt(2) >= 'P') && (s.charAt(2) <= 'S')) {
					s = "\u001b" + s.substring(2); /* ESC x */
				} else {
					s = "\u001b?" + s.substring(2); /* ESC ? x */
				}
			} else {
				if (output8bit) {
					s = "\u008f" + s.substring(2); /* SS3 x */
				} /* else keep string as it is */
			}
		}
		if (((s.length() >= 3) && (s.charAt(0) == 27) && (s.charAt(1) == '['))) {
			if (output8bit) {
				s = "\u009b" + s.substring(2); /* CSI ... */
			} /* else keep */
		}
		return write(s, false);
	}

	/**
	 * main keytyping event handler...
	 */
	public void keyPressed(int keyCode, char keyChar, int modifiers) {
		boolean control = (modifiers & VDUInput.KEY_CONTROL) != 0;
		boolean shift = (modifiers & VDUInput.KEY_SHIFT) != 0;
		boolean alt = (modifiers & VDUInput.KEY_ALT) != 0;

		if (debug > 1) {
			debugStr.append("keyPressed(").append(keyCode).append(", ").append(
					(int) keyChar).append(", ").append(modifiers).append(')');
			debug(debugStr.toString());
			debugStr.setLength(0);
		}

		int xind;
		String fmap[];
		xind = 0;
		fmap = FunctionKey;
		if (shift) {
			fmap = FunctionKeyShift;
			xind = 1;
		}
		if (control) {
			fmap = FunctionKeyCtrl;
			xind = 2;
		}
		if (alt) {
			fmap = FunctionKeyAlt;
			xind = 3;
		}

		switch (keyCode) {
		case KEY_PAUSE:
			if (shift || control)
				sendTelnetCommand((byte) 243); // BREAK
			break;
		case KEY_F1:
			writeSpecial(fmap[1]);
			break;
		case KEY_F2:
			writeSpecial(fmap[2]);
			break;
		case KEY_F3:
			writeSpecial(fmap[3]);
			break;
		case KEY_F4:
			writeSpecial(fmap[4]);
			break;
		case KEY_F5:
			writeSpecial(fmap[5]);
			break;
		case KEY_F6:
			writeSpecial(fmap[6]);
			break;
		case KEY_F7:
			writeSpecial(fmap[7]);
			break;
		case KEY_F8:
			writeSpecial(fmap[8]);
			break;
		case KEY_F9:
			writeSpecial(fmap[9]);
			break;
		case KEY_F10:
			writeSpecial(fmap[10]);
			break;
		case KEY_F11:
			writeSpecial(fmap[11]);
			break;
		case KEY_F12:
			writeSpecial(fmap[12]);
			break;
		case KEY_UP:
			writeSpecial(KeyUp[xind]);
			break;
		case KEY_DOWN:
			writeSpecial(KeyDown[xind]);
			break;
		case KEY_LEFT:
			writeSpecial(KeyLeft[xind]);
			break;
		case KEY_RIGHT:
			writeSpecial(KeyRight[xind]);
			break;
		case KEY_PAGE_DOWN:
			writeSpecial(NextScn[xind]);
			break;
		case KEY_PAGE_UP:
			writeSpecial(PrevScn[xind]);
			break;
		case KEY_INSERT:
			writeSpecial(Insert[xind]);
			break;
		case KEY_DELETE:
			writeSpecial(Remove[xind]);
			break;
		case KEY_BACK_SPACE:
			writeSpecial(BackSpace[xind]);
			if (localecho) {
				if (BackSpace[xind] == "\b") {
					putString("\b \b"); // make the last char 'deleted'
				} else {
					putString(BackSpace[xind]); // echo it
				}
			}
			break;
		case KEY_HOME:
			writeSpecial(KeyHome[xind]);
			break;
		case KEY_END:
			writeSpecial(KeyEnd[xind]);
			break;
		case KEY_NUM_LOCK:
			if (vms && control) {
				writeSpecial(PF1);
			}
			if (!control)
				numlock = !numlock;
			break;
		case KEY_CAPS_LOCK:
			capslock = !capslock;
			return;
		case KEY_SHIFT:
		case KEY_CONTROL:
		case KEY_ALT:
			return;
		default:
			break;
		}
	}

	/*
	 * public void keyReleased(KeyEvent evt) { if (debug > 1)
	 * debug("keyReleased("+evt+")"); // ignore }
	 */
	/**
	 * Handle key Typed events for the terminal, this will get all normal key
	 * types, but no shift/alt/control/numlock.
	 */
	public void keyTyped(int keyCode, char keyChar, int modifiers) {
		boolean control = (modifiers & VDUInput.KEY_CONTROL) != 0;
		boolean shift = (modifiers & VDUInput.KEY_SHIFT) != 0;
		boolean alt = (modifiers & VDUInput.KEY_ALT) != 0;

		if (debug > 1)
			debug("keyTyped(" + keyCode + ", " + (int) keyChar + ", "
					+ modifiers + ")");

		if (keyChar == '\t') {
			if (shift) {
				write(TabKey[1], false);
			} else {
				if (control) {
					write(TabKey[2], false);
				} else {
					if (alt) {
						write(TabKey[3], false);
					} else {
						write(TabKey[0], false);
					}
				}
			}
			return;
		}
		if (alt) {
			write(((char) (keyChar | 0x80)));
			return;
		}

		if (((keyCode == KEY_ENTER) || (keyChar == 10)) && !control) {
			write('\r');
			if (localecho)
				putString("\r\n"); // bad hack
			return;
		}

		if ((keyCode == 10) && !control) {
			debug("Sending \\r");
			write('\r');
			return;
		}

		// FIXME: on german PC keyboards you have to use Alt-Ctrl-q to get an @,
		// so we can't just use it here... will probably break some other VMS
		// codes. -Marcus
		// if(((!vms && keyChar == '2') || keyChar == '@' || keyChar == ' ')
		// && control)
		if (((!vms && keyChar == '2') || keyChar == ' ') && control)
			write(0);

		if (vms) {
			if (keyChar == 127 && !control) {
				if (shift)
					writeSpecial(Insert[0]); // VMS shift delete = insert
				else
					writeSpecial(Remove[0]); // VMS delete = remove
				return;
			} else if (control)
				switch (keyChar) {
				case '0':
					writeSpecial(Numpad[0]);
					return;
				case '1':
					writeSpecial(Numpad[1]);
					return;
				case '2':
					writeSpecial(Numpad[2]);
					return;
				case '3':
					writeSpecial(Numpad[3]);
					return;
				case '4':
					writeSpecial(Numpad[4]);
					return;
				case '5':
					writeSpecial(Numpad[5]);
					return;
				case '6':
					writeSpecial(Numpad[6]);
					return;
				case '7':
					writeSpecial(Numpad[7]);
					return;
				case '8':
					writeSpecial(Numpad[8]);
					return;
				case '9':
					writeSpecial(Numpad[9]);
					return;
				case '.':
					writeSpecial(KPPeriod);
					return;
				case '-':
				case 31:
					writeSpecial(KPMinus);
					return;
				case '+':
					writeSpecial(KPComma);
					return;
				case 10:
					writeSpecial(KPEnter);
					return;
				case '/':
					writeSpecial(PF2);
					return;
				case '*':
					writeSpecial(PF3);
					return;
					/* NUMLOCK handled in keyPressed */
				default:
					break;
				}
			/*
			 * Now what does this do and how did it get here. -Marcus if (shift
			 * && keyChar < 32) { write(PF1+(char)(keyChar + 64)); return; }
			 */
		}

		// FIXME: not used?
		// String fmap[];
		int xind;
		xind = 0;
		// fmap = FunctionKey;
		if (shift) {
			// fmap = FunctionKeyShift;
			xind = 1;
		}
		if (control) {
			// fmap = FunctionKeyCtrl;
			xind = 2;
		}
		if (alt) {
			// fmap = FunctionKeyAlt;
			xind = 3;
		}

		if (keyCode == KEY_ESCAPE) {
			writeSpecial(Escape[xind]);
			return;
		}

		if ((modifiers & VDUInput.KEY_ACTION) != 0)
			switch (keyCode) {
			case KEY_NUMPAD0:
				writeSpecial(Numpad[0]);
				return;
			case KEY_NUMPAD1:
				writeSpecial(Numpad[1]);
				return;
			case KEY_NUMPAD2:
				writeSpecial(Numpad[2]);
				return;
			case KEY_NUMPAD3:
				writeSpecial(Numpad[3]);
				return;
			case KEY_NUMPAD4:
				writeSpecial(Numpad[4]);
				return;
			case KEY_NUMPAD5:
				writeSpecial(Numpad[5]);
				return;
			case KEY_NUMPAD6:
				writeSpecial(Numpad[6]);
				return;
			case KEY_NUMPAD7:
				writeSpecial(Numpad[7]);
				return;
			case KEY_NUMPAD8:
				writeSpecial(Numpad[8]);
				return;
			case KEY_NUMPAD9:
				writeSpecial(Numpad[9]);
				return;
			case KEY_DECIMAL:
				writeSpecial(NUMDot[xind]);
				return;
			case KEY_ADD:
				writeSpecial(NUMPlus[xind]);
				return;
			}

		if (!((keyChar == 8) || (keyChar == 127) || (keyChar == '\r') || (keyChar == '\n'))) {
			write(keyChar);
			return;
		}
	}

	private void handle_dcs(String dcs) {
		debugStr.append("DCS: ").append(dcs);
		debug(debugStr.toString());
		debugStr.setLength(0);
	}

	private void handle_osc(String osc) {
		if (osc.length() > 2 && osc.substring(0, 2).equals("4;")) {
			// Define color palette
			String[] colorData = osc.split(";");

			try {
				int colorIndex = Integer.parseInt(colorData[1]);

				if ("rgb:".equals(colorData[2].substring(0, 4))) {
					String[] rgb = colorData[2].substring(4).split("/");

					int red = Integer.parseInt(rgb[0].substring(0, 2), 16) & 0xFF;
					int green = Integer.parseInt(rgb[1].substring(0, 2), 16) & 0xFF;
					int blue = Integer.parseInt(rgb[2].substring(0, 2), 16) & 0xFF;
					display.setColor(colorIndex, red, green, blue);
				}
			} catch (Exception e) {
				debugStr.append("OSC: invalid color sequence encountered: ")
						.append(osc);
				debug(debugStr.toString());
				debugStr.setLength(0);
			}
		} else
			debug("OSC: " + osc);
	}

	private final static char unimap[] = {
	// #
			// # Name: cp437_DOSLatinUS to Unicode table
			// # Unicode version: 1.1
			// # Table version: 1.1
			// # Table format: Format A
			// # Date: 03/31/95
			// # Authors: Michel Suignard <michelsu@microsoft.com>
			// # Lori Hoerth <lorih@microsoft.com>
			// # General notes: none
			// #
			// # Format: Three tab-separated columns
			// # Column #1 is the cp1255_WinHebrew code (in hex)
			// # Column #2 is the Unicode (in hex as 0xXXXX)
			// # Column #3 is the Unicode name (follows a comment sign, '#')
			// #
			// # The entries are in cp437_DOSLatinUS order
			// #

			0x0000, // #NULL
			0x0001, // #START OF HEADING
			0x0002, // #START OF TEXT
			0x0003, // #END OF TEXT
			0x0004, // #END OF TRANSMISSION
			0x0005, // #ENQUIRY
			0x0006, // #ACKNOWLEDGE
			0x0007, // #BELL
			0x0008, // #BACKSPACE
			0x0009, // #HORIZONTAL TABULATION
			0x000a, // #LINE FEED
			0x000b, // #VERTICAL TABULATION
			0x000c, // #FORM FEED
			0x000d, // #CARRIAGE RETURN
			0x000e, // #SHIFT OUT
			0x000f, // #SHIFT IN
			0x0010, // #DATA LINK ESCAPE
			0x0011, // #DEVICE CONTROL ONE
			0x0012, // #DEVICE CONTROL TWO
			0x0013, // #DEVICE CONTROL THREE
			0x0014, // #DEVICE CONTROL FOUR
			0x0015, // #NEGATIVE ACKNOWLEDGE
			0x0016, // #SYNCHRONOUS IDLE
			0x0017, // #END OF TRANSMISSION BLOCK
			0x0018, // #CANCEL
			0x0019, // #END OF MEDIUM
			0x001a, // #SUBSTITUTE
			0x001b, // #ESCAPE
			0x001c, // #FILE SEPARATOR
			0x001d, // #GROUP SEPARATOR
			0x001e, // #RECORD SEPARATOR
			0x001f, // #UNIT SEPARATOR
			0x0020, // #SPACE
			0x0021, // #EXCLAMATION MARK
			0x0022, // #QUOTATION MARK
			0x0023, // #NUMBER SIGN
			0x0024, // #DOLLAR SIGN
			0x0025, // #PERCENT SIGN
			0x0026, // #AMPERSAND
			0x0027, // #APOSTROPHE
			0x0028, // #LEFT PARENTHESIS
			0x0029, // #RIGHT PARENTHESIS
			0x002a, // #ASTERISK
			0x002b, // #PLUS SIGN
			0x002c, // #COMMA
			0x002d, // #HYPHEN-MINUS
			0x002e, // #FULL STOP
			0x002f, // #SOLIDUS
			0x0030, // #DIGIT ZERO
			0x0031, // #DIGIT ONE
			0x0032, // #DIGIT TWO
			0x0033, // #DIGIT THREE
			0x0034, // #DIGIT FOUR
			0x0035, // #DIGIT FIVE
			0x0036, // #DIGIT SIX
			0x0037, // #DIGIT SEVEN
			0x0038, // #DIGIT EIGHT
			0x0039, // #DIGIT NINE
			0x003a, // #COLON
			0x003b, // #SEMICOLON
			0x003c, // #LESS-THAN SIGN
			0x003d, // #EQUALS SIGN
			0x003e, // #GREATER-THAN SIGN
			0x003f, // #QUESTION MARK
			0x0040, // #COMMERCIAL AT
			0x0041, // #LATIN CAPITAL LETTER A
			0x0042, // #LATIN CAPITAL LETTER B
			0x0043, // #LATIN CAPITAL LETTER C
			0x0044, // #LATIN CAPITAL LETTER D
			0x0045, // #LATIN CAPITAL LETTER E
			0x0046, // #LATIN CAPITAL LETTER F
			0x0047, // #LATIN CAPITAL LETTER G
			0x0048, // #LATIN CAPITAL LETTER H
			0x0049, // #LATIN CAPITAL LETTER I
			0x004a, // #LATIN CAPITAL LETTER J
			0x004b, // #LATIN CAPITAL LETTER K
			0x004c, // #LATIN CAPITAL LETTER L
			0x004d, // #LATIN CAPITAL LETTER M
			0x004e, // #LATIN CAPITAL LETTER N
			0x004f, // #LATIN CAPITAL LETTER O
			0x0050, // #LATIN CAPITAL LETTER P
			0x0051, // #LATIN CAPITAL LETTER Q
			0x0052, // #LATIN CAPITAL LETTER R
			0x0053, // #LATIN CAPITAL LETTER S
			0x0054, // #LATIN CAPITAL LETTER T
			0x0055, // #LATIN CAPITAL LETTER U
			0x0056, // #LATIN CAPITAL LETTER V
			0x0057, // #LATIN CAPITAL LETTER W
			0x0058, // #LATIN CAPITAL LETTER X
			0x0059, // #LATIN CAPITAL LETTER Y
			0x005a, // #LATIN CAPITAL LETTER Z
			0x005b, // #LEFT SQUARE BRACKET
			0x005c, // #REVERSE SOLIDUS
			0x005d, // #RIGHT SQUARE BRACKET
			0x005e, // #CIRCUMFLEX ACCENT
			0x005f, // #LOW LINE
			0x0060, // #GRAVE ACCENT
			0x0061, // #LATIN SMALL LETTER A
			0x0062, // #LATIN SMALL LETTER B
			0x0063, // #LATIN SMALL LETTER C
			0x0064, // #LATIN SMALL LETTER D
			0x0065, // #LATIN SMALL LETTER E
			0x0066, // #LATIN SMALL LETTER F
			0x0067, // #LATIN SMALL LETTER G
			0x0068, // #LATIN SMALL LETTER H
			0x0069, // #LATIN SMALL LETTER I
			0x006a, // #LATIN SMALL LETTER J
			0x006b, // #LATIN SMALL LETTER K
			0x006c, // #LATIN SMALL LETTER L
			0x006d, // #LATIN SMALL LETTER M
			0x006e, // #LATIN SMALL LETTER N
			0x006f, // #LATIN SMALL LETTER O
			0x0070, // #LATIN SMALL LETTER P
			0x0071, // #LATIN SMALL LETTER Q
			0x0072, // #LATIN SMALL LETTER R
			0x0073, // #LATIN SMALL LETTER S
			0x0074, // #LATIN SMALL LETTER T
			0x0075, // #LATIN SMALL LETTER U
			0x0076, // #LATIN SMALL LETTER V
			0x0077, // #LATIN SMALL LETTER W
			0x0078, // #LATIN SMALL LETTER X
			0x0079, // #LATIN SMALL LETTER Y
			0x007a, // #LATIN SMALL LETTER Z
			0x007b, // #LEFT CURLY BRACKET
			0x007c, // #VERTICAL LINE
			0x007d, // #RIGHT CURLY BRACKET
			0x007e, // #TILDE
			0x007f, // #DELETE
			0x00c7, // #LATIN CAPITAL LETTER C WITH CEDILLA
			0x00fc, // #LATIN SMALL LETTER U WITH DIAERESIS
			0x00e9, // #LATIN SMALL LETTER E WITH ACUTE
			0x00e2, // #LATIN SMALL LETTER A WITH CIRCUMFLEX
			0x00e4, // #LATIN SMALL LETTER A WITH DIAERESIS
			0x00e0, // #LATIN SMALL LETTER A WITH GRAVE
			0x00e5, // #LATIN SMALL LETTER A WITH RING ABOVE
			0x00e7, // #LATIN SMALL LETTER C WITH CEDILLA
			0x00ea, // #LATIN SMALL LETTER E WITH CIRCUMFLEX
			0x00eb, // #LATIN SMALL LETTER E WITH DIAERESIS
			0x00e8, // #LATIN SMALL LETTER E WITH GRAVE
			0x00ef, // #LATIN SMALL LETTER I WITH DIAERESIS
			0x00ee, // #LATIN SMALL LETTER I WITH CIRCUMFLEX
			0x00ec, // #LATIN SMALL LETTER I WITH GRAVE
			0x00c4, // #LATIN CAPITAL LETTER A WITH DIAERESIS
			0x00c5, // #LATIN CAPITAL LETTER A WITH RING ABOVE
			0x00c9, // #LATIN CAPITAL LETTER E WITH ACUTE
			0x00e6, // #LATIN SMALL LIGATURE AE
			0x00c6, // #LATIN CAPITAL LIGATURE AE
			0x00f4, // #LATIN SMALL LETTER O WITH CIRCUMFLEX
			0x00f6, // #LATIN SMALL LETTER O WITH DIAERESIS
			0x00f2, // #LATIN SMALL LETTER O WITH GRAVE
			0x00fb, // #LATIN SMALL LETTER U WITH CIRCUMFLEX
			0x00f9, // #LATIN SMALL LETTER U WITH GRAVE
			0x00ff, // #LATIN SMALL LETTER Y WITH DIAERESIS
			0x00d6, // #LATIN CAPITAL LETTER O WITH DIAERESIS
			0x00dc, // #LATIN CAPITAL LETTER U WITH DIAERESIS
			0x00a2, // #CENT SIGN
			0x00a3, // #POUND SIGN
			0x00a5, // #YEN SIGN
			0x20a7, // #PESETA SIGN
			0x0192, // #LATIN SMALL LETTER F WITH HOOK
			0x00e1, // #LATIN SMALL LETTER A WITH ACUTE
			0x00ed, // #LATIN SMALL LETTER I WITH ACUTE
			0x00f3, // #LATIN SMALL LETTER O WITH ACUTE
			0x00fa, // #LATIN SMALL LETTER U WITH ACUTE
			0x00f1, // #LATIN SMALL LETTER N WITH TILDE
			0x00d1, // #LATIN CAPITAL LETTER N WITH TILDE
			0x00aa, // #FEMININE ORDINAL INDICATOR
			0x00ba, // #MASCULINE ORDINAL INDICATOR
			0x00bf, // #INVERTED QUESTION MARK
			0x2310, // #REVERSED NOT SIGN
			0x00ac, // #NOT SIGN
			0x00bd, // #VULGAR FRACTION ONE HALF
			0x00bc, // #VULGAR FRACTION ONE QUARTER
			0x00a1, // #INVERTED EXCLAMATION MARK
			0x00ab, // #LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
			0x00bb, // #RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
			0x2591, // #LIGHT SHADE
			0x2592, // #MEDIUM SHADE
			0x2593, // #DARK SHADE
			0x2502, // #BOX DRAWINGS LIGHT VERTICAL
			0x2524, // #BOX DRAWINGS LIGHT VERTICAL AND LEFT
			0x2561, // #BOX DRAWINGS VERTICAL SINGLE AND LEFT DOUBLE
			0x2562, // #BOX DRAWINGS VERTICAL DOUBLE AND LEFT SINGLE
			0x2556, // #BOX DRAWINGS DOWN DOUBLE AND LEFT SINGLE
			0x2555, // #BOX DRAWINGS DOWN SINGLE AND LEFT DOUBLE
			0x2563, // #BOX DRAWINGS DOUBLE VERTICAL AND LEFT
			0x2551, // #BOX DRAWINGS DOUBLE VERTICAL
			0x2557, // #BOX DRAWINGS DOUBLE DOWN AND LEFT
			0x255d, // #BOX DRAWINGS DOUBLE UP AND LEFT
			0x255c, // #BOX DRAWINGS UP DOUBLE AND LEFT SINGLE
			0x255b, // #BOX DRAWINGS UP SINGLE AND LEFT DOUBLE
			0x2510, // #BOX DRAWINGS LIGHT DOWN AND LEFT
			0x2514, // #BOX DRAWINGS LIGHT UP AND RIGHT
			0x2534, // #BOX DRAWINGS LIGHT UP AND HORIZONTAL
			0x252c, // #BOX DRAWINGS LIGHT DOWN AND HORIZONTAL
			0x251c, // #BOX DRAWINGS LIGHT VERTICAL AND RIGHT
			0x2500, // #BOX DRAWINGS LIGHT HORIZONTAL
			0x253c, // #BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL
			0x255e, // #BOX DRAWINGS VERTICAL SINGLE AND RIGHT DOUBLE
			0x255f, // #BOX DRAWINGS VERTICAL DOUBLE AND RIGHT SINGLE
			0x255a, // #BOX DRAWINGS DOUBLE UP AND RIGHT
			0x2554, // #BOX DRAWINGS DOUBLE DOWN AND RIGHT
			0x2569, // #BOX DRAWINGS DOUBLE UP AND HORIZONTAL
			0x2566, // #BOX DRAWINGS DOUBLE DOWN AND HORIZONTAL
			0x2560, // #BOX DRAWINGS DOUBLE VERTICAL AND RIGHT
			0x2550, // #BOX DRAWINGS DOUBLE HORIZONTAL
			0x256c, // #BOX DRAWINGS DOUBLE VERTICAL AND HORIZONTAL
			0x2567, // #BOX DRAWINGS UP SINGLE AND HORIZONTAL DOUBLE
			0x2568, // #BOX DRAWINGS UP DOUBLE AND HORIZONTAL SINGLE
			0x2564, // #BOX DRAWINGS DOWN SINGLE AND HORIZONTAL DOUBLE
			0x2565, // #BOX DRAWINGS DOWN DOUBLE AND HORIZONTAL SINGLE
			0x2559, // #BOX DRAWINGS UP DOUBLE AND RIGHT SINGLE
			0x2558, // #BOX DRAWINGS UP SINGLE AND RIGHT DOUBLE
			0x2552, // #BOX DRAWINGS DOWN SINGLE AND RIGHT DOUBLE
			0x2553, // #BOX DRAWINGS DOWN DOUBLE AND RIGHT SINGLE
			0x256b, // #BOX DRAWINGS VERTICAL DOUBLE AND HORIZONTAL SINGLE
			0x256a, // #BOX DRAWINGS VERTICAL SINGLE AND HORIZONTAL DOUBLE
			0x2518, // #BOX DRAWINGS LIGHT UP AND LEFT
			0x250c, // #BOX DRAWINGS LIGHT DOWN AND RIGHT
			0x2588, // #FULL BLOCK
			0x2584, // #LOWER HALF BLOCK
			0x258c, // #LEFT HALF BLOCK
			0x2590, // #RIGHT HALF BLOCK
			0x2580, // #UPPER HALF BLOCK
			0x03b1, // #GREEK SMALL LETTER ALPHA
			0x00df, // #LATIN SMALL LETTER SHARP S
			0x0393, // #GREEK CAPITAL LETTER GAMMA
			0x03c0, // #GREEK SMALL LETTER PI
			0x03a3, // #GREEK CAPITAL LETTER SIGMA
			0x03c3, // #GREEK SMALL LETTER SIGMA
			0x00b5, // #MICRO SIGN
			0x03c4, // #GREEK SMALL LETTER TAU
			0x03a6, // #GREEK CAPITAL LETTER PHI
			0x0398, // #GREEK CAPITAL LETTER THETA
			0x03a9, // #GREEK CAPITAL LETTER OMEGA
			0x03b4, // #GREEK SMALL LETTER DELTA
			0x221e, // #INFINITY
			0x03c6, // #GREEK SMALL LETTER PHI
			0x03b5, // #GREEK SMALL LETTER EPSILON
			0x2229, // #INTERSECTION
			0x2261, // #IDENTICAL TO
			0x00b1, // #PLUS-MINUS SIGN
			0x2265, // #GREATER-THAN OR EQUAL TO
			0x2264, // #LESS-THAN OR EQUAL TO
			0x2320, // #TOP HALF INTEGRAL
			0x2321, // #BOTTOM HALF INTEGRAL
			0x00f7, // #DIVISION SIGN
			0x2248, // #ALMOST EQUAL TO
			0x00b0, // #DEGREE SIGN
			0x2219, // #BULLET OPERATOR
			0x00b7, // #MIDDLE DOT
			0x221a, // #SQUARE ROOT
			0x207f, // #SUPERSCRIPT LATIN SMALL LETTER N
			0x00b2, // #SUPERSCRIPT TWO
			0x25a0, // #BLACK SQUARE
			0x00a0, // #NO-BREAK SPACE
	};

	public char map_cp850_unicode(char x) {
		if (x >= 0x100)
			return x;
		return unimap[x];
	}

	private void _SetCursor(int row, int col) {
		int maxr = height - 1;
		int tm = getTopMargin();

		R = (row < 0) ? 0 : row;
		C = (col < 0) ? 0 : (col >= width) ? width - 1 : col;

		if (!moveoutsidemargins) {
			R += tm;
			maxr = getBottomMargin();
		}
		if (R > maxr)
			R = maxr;
	}

	private void putChar(char c, boolean isWide, boolean doshowcursor) {
		int rows = this.height; // statusline
		int columns = this.width;

		switch (term_state) {
		case TSTATE_DATA:
			if (!useibmcharset) {
				boolean doneflag = true;
				switch (c) {
				case OSC:
					osc = "";
					term_state = TSTATE_OSC;
					break;
				case RI:
					if (R > getTopMargin())
						R--;
					else
						insertLine(R, 1, SCROLL_DOWN);
					if (debug > 1)
						debug("RI");
					break;
				case IND:
					if (debug > 2) {
						debugStr.append("IND at ").append(R).append(", tm is ")
								.append(getTopMargin()).append(", bm is ")
								.append(getBottomMargin());
						debug(debugStr.toString());
						debugStr.setLength(0);
					}
					if (R == getBottomMargin() || R == rows - 1)
						insertLine(R, 1, SCROLL_UP);
					else
						R++;
					if (debug > 1)
						debug("IND (at " + R + " )");
					break;
				case NEL:
					if (R == getBottomMargin() || R == rows - 1)
						insertLine(R, 1, SCROLL_UP);
					else
						R++;
					C = 0;
					if (debug > 1)
						debug("NEL (at " + R + " )");
					break;
				case HTS:
					Tabs[C] = 1;
					if (debug > 1)
						debug("HTS");
					break;
				case DCS:
					dcs = "";
					term_state = TSTATE_DCS;
					break;
				default:
					doneflag = false;
					break;
				}
				if (doneflag)
					break;
			}
			switch (c) {
			case SS3:
				onegl = 3;
				break;
			case SS2:
				onegl = 2;
				break;
			case CSI: // should be in the 8bit section, but some BBS use this
				DCEvar = 0;
				DCEvars[0] = 0;
				DCEvars[1] = 0;
				DCEvars[2] = 0;
				DCEvars[3] = 0;
				term_state = TSTATE_CSI;
				break;
			case ESC:
				term_state = TSTATE_ESC;
				lastwaslf = 0;
				break;
			case 5: /* ENQ */
				write(answerBack, false);
				break;
			case 12:
				/* FormFeed, Home for the BBS world */
				deleteArea(0, 0, columns, rows, attributes);
				C = R = 0;
				break;
			case '\b': /* 8 */
				C--;
				if (C < 0)
					C = 0;
				lastwaslf = 0;
				break;
			case '\t':
				do {
					// Don't overwrite or insert! TABS are not destructive, but
					// movement!
					C++;
				} while (C < columns && (Tabs[C] == 0));
				lastwaslf = 0;
				break;
			case '\r': // 13 CR
				C = 0;
				break;
			case '\n': // 10 LF
				if (debug > 3)
					debug("R= " + R + ", bm " + getBottomMargin() + ", tm="
							+ getTopMargin() + ", rows=" + rows);
				if (!vms) {
					if (lastwaslf != 0 && lastwaslf != c) // Ray: I do not
						// understand this
						// logic.
						break;
					lastwaslf = c;
					/* C = 0; */
				}
				if (R == getBottomMargin() || R >= rows - 1)
					insertLine(R, 1, SCROLL_UP);
				else {
					strBuff += "\n";
					R++;
				}
				break;
			case 7:
				beep();
				break;
			case '\016': /* SMACS , as */
				/* ^N, Shift out - Put G1 into GL */
				gl = 1;
				usedcharsets = true;
				break;
			case '\017': /* RMACS , ae */
				/* ^O, Shift in - Put G0 into GL */
				gl = 0;
				usedcharsets = true;
				break;
			default: {
				int thisgl = gl;

				if (onegl >= 0) {
					thisgl = onegl;
					onegl = -1;
				}
				lastwaslf = 0;
				if (c < 32) {
					if (c != 0)
						if (debug > 0)
							debug("TSTATE_DATA char: " + ((int) c));
					/*
					 * break; some BBS really want those characters, like hearst
					 * etc.
					 */
					if (c == 0) /* print 0 ... you bet */
						break;
				}
				if (C >= columns) {
					if (wraparound) {
						int bot = rows;

						// If we're in the scroll region, check against the
						// bottom margin
						if (R <= getBottomMargin() && R >= getTopMargin())
							bot = getBottomMargin() + 1;

						if (R < bot - 1)
							R++;
						else {
							if (debug > 3)
								debug("scrolling due to wrap at " + R);
							insertLine(R, 1, SCROLL_UP);
						}
						C = 0;
					} else {
						// cursor stays on last character.
						C = columns - 1;
					}
				}

				boolean mapped = false;

				// Mapping if DEC Special is chosen charset
				if (usedcharsets) {
					if (c >= '\u0020' && c <= '\u007f') {
						switch (gx[thisgl]) {
						case '0':
							// Remap SCOANSI line drawing to VT100 line drawing
							// chars
							// for our SCO using customers.
							if (terminalID.equals("scoansi")
									|| terminalID.equals("ansi")) {
								for (int i = 0; i < scoansi_acs.length(); i += 2) {
									if (c == scoansi_acs.charAt(i)) {
										c = scoansi_acs.charAt(i + 1);
										break;
									}
								}
							}
							if (c >= '\u005f' && c <= '\u007e') {
								c = DECSPECIAL[(short) c - 0x5f];
								mapped = true;
							}
							break;
						case '<': // 'user preferred' is currently 'ISO Latin-1
							// suppl
							c = (char) ((c & 0x7f) | 0x80);
							mapped = true;
							break;
						case 'A':
						case 'B': // Latin-1 , ASCII -> fall through
							mapped = true;
							break;
						default:
							debug("Unsupported GL mapping: " + gx[thisgl]);
							break;
						}
					}
					if (!mapped && (c >= '\u0080' && c <= '\u00ff')) {
						switch (gx[gr]) {
						case '0':
							if (c >= '\u00df' && c <= '\u00fe') {
								c = DECSPECIAL[c - '\u00df'];
								mapped = true;
							}
							break;
						case '<':
						case 'A':
						case 'B':
							mapped = true;
							break;
						default:
							debug("Unsupported GR mapping: " + gx[gr]);
							break;
						}
					}
				}
				if (!mapped && useibmcharset)
					c = map_cp850_unicode(c);

				/* if(true || (statusmode == 0)) { */
				if (isWide) {
					if (C >= columns - 1) {
						if (wraparound) {
							int bot = rows;

							// If we're in the scroll region, check against the
							// bottom margin
							if (R <= getBottomMargin() && R >= getTopMargin())
								bot = getBottomMargin() + 1;

							if (R < bot - 1)
								R++;
							else {
								if (debug > 3)
									debug("scrolling due to wrap at " + R);
								insertLine(R, 1, SCROLL_UP);
							}
							C = 0;
						} else {
							// cursor stays on last wide character.
							C = columns - 2;
						}
					}
				}

				if (insertmode == 1) {
					if (isWide) {
						insertChar(C++, R, c, attributes | FULLWIDTH);
						insertChar(C, R, ' ', attributes | FULLWIDTH);
					} else
						insertChar(C, R, c, attributes);
				} else {
					if (isWide) {
						putChar(C++, R, c, attributes | FULLWIDTH);
						strBuff += c;
						putChar(C, R, ' ', attributes | FULLWIDTH);
					} else {
						putChar(C, R, c, attributes);
						strBuff += c;
					}

				}

				C++;
				break;
			}
			} /* switch(c) */
			break;
		case TSTATE_OSC:
			if ((c < 0x20) && (c != ESC)) {// NP - No printing character
				handle_osc(osc);
				term_state = TSTATE_DATA;
				break;
			}
			// but check for vt102 ESC \
			if (c == '\\' && osc.charAt(osc.length() - 1) == ESC) {
				handle_osc(osc);
				term_state = TSTATE_DATA;
				break;
			}
			osc = osc + c;
			break;
		case TSTATE_ESCSPACE:
			term_state = TSTATE_DATA;
			switch (c) {
			case 'F': /* S7C1T, Disable output of 8-bit controls, use 7-bit */
				output8bit = false;
				break;
			case 'G': /* S8C1T, Enable output of 8-bit control codes */
				output8bit = true;
				break;
			default:
				debug("ESC <space> " + c + " unhandled.");
			}
			break;
		case TSTATE_ESC:
			term_state = TSTATE_DATA;
			switch (c) {
			case ' ':
				term_state = TSTATE_ESCSPACE;
				break;
			case '#':
				term_state = TSTATE_ESCSQUARE;
				break;
			case 'c':
				/* Hard terminal reset */
				reset();
				break;
			case '[':
				DCEvar = 0;
				DCEvars[0] = 0;
				DCEvars[1] = 0;
				DCEvars[2] = 0;
				DCEvars[3] = 0;
				term_state = TSTATE_CSI;
				break;
			case ']':
				osc = "";
				term_state = TSTATE_OSC;
				break;
			case 'P':
				dcs = "";
				term_state = TSTATE_DCS;
				break;
			case 'A': /* CUU */
				R--;
				if (R < 0)
					R = 0;
				break;
			case 'B': /* CUD */
				R++;
				if (R >= rows)
					R = rows - 1;
				break;
			case 'C':
				C++;
				if (C >= columns)
					C = columns - 1;
				break;
			case 'I': // RI
				insertLine(R, 1, SCROLL_DOWN);
				break;
			case 'E': /* NEL */
				if (R == getBottomMargin() || R == rows - 1)
					insertLine(R, 1, SCROLL_UP);
				else
					R++;
				C = 0;
				if (debug > 1)
					debug("ESC E (at " + R + ")");
				break;
			case 'D': /* IND */
				if (R == getBottomMargin() || R == rows - 1)
					insertLine(R, 1, SCROLL_UP);
				else
					R++;
				if (debug > 1)
					debug("ESC D (at " + R + " )");
				break;
			case 'J': /* erase to end of screen */
				if (R < rows - 1)
					deleteArea(0, R + 1, columns, rows - R - 1, attributes);
				if (C < columns - 1)
					deleteArea(C, R, columns - C, 1, attributes);
				break;
			case 'K':
				if (C < columns - 1)
					deleteArea(C, R, columns - C, 1, attributes);
				break;
			case 'M': // RI
				debug("ESC M : R is " + R + ", tm is " + getTopMargin()
						+ ", bm is " + getBottomMargin());
				if (R > getTopMargin()) { // just go up 1 line.
					R--;
				} else { // scroll down
					insertLine(R, 1, SCROLL_DOWN);
				}
				/* else do nothing ; */
				if (debug > 2)
					debug("ESC M ");
				break;
			case 'H':
				if (debug > 1)
					debug("ESC H at " + C);
				/* right border probably ... */
				if (C >= columns)
					C = columns - 1;
				Tabs[C] = 1;
				break;
			case 'N': // SS2
				onegl = 2;
				break;
			case 'O': // SS3
				onegl = 3;
				break;
			case '=':
				/* application keypad */
				if (debug > 0)
					debug("ESC =");
				keypadmode = true;
				break;
			case '<': /* vt52 mode off */
				vt52mode = false;
				break;
			case '>': /* normal keypad */
				if (debug > 0)
					debug("ESC >");
				keypadmode = false;
				break;
			case '7': /* DECSC: save cursor, attributes */
				Sc = C;
				Sr = R;
				Sgl = gl;
				Sgr = gr;
				Sa = attributes;
				Sgx = new char[4];
				for (int i = 0; i < 4; i++)
					Sgx[i] = gx[i];
				if (debug > 1)
					debug("ESC 7");
				break;
			case '8': /* DECRC: restore cursor, attributes */
				C = Sc;
				R = Sr;
				gl = Sgl;
				gr = Sgr;
				if (Sgx != null)
					for (int i = 0; i < 4; i++)
						gx[i] = Sgx[i];
				attributes = Sa;
				if (debug > 1)
					debug("ESC 8");
				break;
			case '(': /* Designate G0 Character set (ISO 2022) */
				term_state = TSTATE_SETG0;
				usedcharsets = true;
				break;
			case ')': /* Designate G1 character set (ISO 2022) */
				term_state = TSTATE_SETG1;
				usedcharsets = true;
				break;
			case '*': /* Designate G2 Character set (ISO 2022) */
				term_state = TSTATE_SETG2;
				usedcharsets = true;
				break;
			case '+': /* Designate G3 Character set (ISO 2022) */
				term_state = TSTATE_SETG3;
				usedcharsets = true;
				break;
			case '~': /* Locking Shift 1, right */
				gr = 1;
				usedcharsets = true;
				break;
			case 'n': /* Locking Shift 2 */
				gl = 2;
				usedcharsets = true;
				break;
			case '}': /* Locking Shift 2, right */
				gr = 2;
				usedcharsets = true;
				break;
			case 'o': /* Locking Shift 3 */
				gl = 3;
				usedcharsets = true;
				break;
			case '|': /* Locking Shift 3, right */
				gr = 3;
				usedcharsets = true;
				break;
			case 'Y': /* vt52 cursor address mode , next chars are x,y */
				term_state = TSTATE_VT52Y;
				break;
			case '_':
				term_state = TSTATE_TITLE;
				break;
			case '\\':
				// TODO save title
				term_state = TSTATE_DATA;
				break;
			default:
				debug("ESC unknown letter: " + c + " (" + ((int) c) + ")");
				break;
			}
			break;
		case TSTATE_VT52X:
			C = c - 37;
			if (C < 0)
				C = 0;
			else if (C >= width)
				C = width - 1;
			term_state = TSTATE_VT52Y;
			break;
		case TSTATE_VT52Y:
			R = c - 37;
			if (R < 0)
				R = 0;
			else if (R >= height)
				R = height - 1;
			term_state = TSTATE_DATA;
			break;
		case TSTATE_SETG0:
			if (c != '0' && c != 'A' && c != 'B' && c != '<')
				debug("ESC ( " + c + ": G0 char set?  (" + ((int) c) + ")");
			else {
				if (debug > 2)
					debug("ESC ( : G0 char set  (" + c + " " + ((int) c) + ")");
				gx[0] = c;
			}
			term_state = TSTATE_DATA;
			break;
		case TSTATE_SETG1:
			if (c != '0' && c != 'A' && c != 'B' && c != '<') {
				debug("ESC ) " + c + " (" + ((int) c) + ") :G1 char set?");
			} else {
				if (debug > 2)
					debug("ESC ) :G1 char set  (" + c + " " + ((int) c) + ")");
				gx[1] = c;
			}
			term_state = TSTATE_DATA;
			break;
		case TSTATE_SETG2:
			if (c != '0' && c != 'A' && c != 'B' && c != '<')
				debug("ESC*:G2 char set?  (" + ((int) c) + ")");
			else {
				if (debug > 2)
					debug("ESC*:G2 char set  (" + c + " " + ((int) c) + ")");
				gx[2] = c;
			}
			term_state = TSTATE_DATA;
			break;
		case TSTATE_SETG3:
			if (c != '0' && c != 'A' && c != 'B' && c != '<')
				debug("ESC+:G3 char set?  (" + ((int) c) + ")");
			else {
				if (debug > 2)
					debug("ESC+:G3 char set  (" + c + " " + ((int) c) + ")");
				gx[3] = c;
			}
			term_state = TSTATE_DATA;
			break;
		case TSTATE_ESCSQUARE:
			switch (c) {
			case '8':
				for (int i = 0; i < columns; i++)
					for (int j = 0; j < rows; j++)
						putChar(i, j, 'E', 0);
				break;
			default:
				debug("ESC # " + c + " not supported.");
				break;
			}
			term_state = TSTATE_DATA;
			break;
		case TSTATE_DCS:
			if (c == '\\' && dcs.charAt(dcs.length() - 1) == ESC) {
				handle_dcs(dcs);
				term_state = TSTATE_DATA;
				break;
			}
			dcs = dcs + c;
			break;

		case TSTATE_DCEQ:
			term_state = TSTATE_DATA;
			switch (c) {
			case '0':
			case '1':
			case '2':
			case '3':
			case '4':
			case '5':
			case '6':
			case '7':
			case '8':
			case '9':
				DCEvars[DCEvar] = DCEvars[DCEvar] * 10 + (c) - 48;
				term_state = TSTATE_DCEQ;
				break;
			case ';':
				DCEvar++;
				DCEvars[DCEvar] = 0;
				term_state = TSTATE_DCEQ;
				break;
			case 's': // XTERM_SAVE missing!
				if (true || debug > 1)
					debug("ESC [ ? " + DCEvars[0] + " s unimplemented!");
				break;
			case 'r': // XTERM_RESTORE
				if (true || debug > 1)
					debug("ESC [ ? " + DCEvars[0] + " r");
				/* DEC Mode reset */
				for (int i = 0; i <= DCEvar; i++) {
					switch (DCEvars[i]) {
					case 3: /* 80 columns */
						setScreenSize(80, height, true);
						break;
					case 4: /* scrolling mode, smooth */
						break;
					case 5: /* light background */
						break;
					case 6: /* DECOM (Origin Mode) move inside margins. */
						moveoutsidemargins = true;
						break;
					case 7: /* DECAWM: Autowrap Mode */
						wraparound = false;
						break;
					case 12:/* local echo off */
						break;
					case 9: /* X10 mouse */
					case 1000: /* xterm style mouse report on */
					case 1001:
					case 1002:
					case 1003:
						mouserpt = DCEvars[i];
						break;
					default:
						debug("ESC [ ? " + DCEvars[0] + " r, unimplemented!");
					}
				}
				break;
			case 'h': // DECSET
				if (debug > 0)
					debug("ESC [ ? " + DCEvars[0] + " h");
				/* DEC Mode set */
				for (int i = 0; i <= DCEvar; i++) {
					switch (DCEvars[i]) {
					case 1: /* Application cursor keys */
						KeyUp[0] = "\u001bOA";
						KeyDown[0] = "\u001bOB";
						KeyRight[0] = "\u001bOC";
						KeyLeft[0] = "\u001bOD";
						break;
					case 2: /* DECANM */
						vt52mode = false;
						break;
					case 3: /* 132 columns */
						setScreenSize(132, height, true);
						break;
					case 6: /* DECOM: move inside margins. */
						moveoutsidemargins = false;
						break;
					case 7: /* DECAWM: Autowrap Mode */
						wraparound = true;
						break;
					case 25: /* turn cursor on */
						showCursor(true);
						break;
					case 9: /* X10 mouse */
					case 1000: /* xterm style mouse report on */
					case 1001:
					case 1002:
					case 1003:
						mouserpt = DCEvars[i];
						break;

					/* unimplemented stuff, fall through */
					/* 4 - scrolling mode, smooth */
					/* 5 - light background */
					/* 12 - local echo off */
					/* 18 - DECPFF - Printer Form Feed Mode -> On */
					/* 19 - DECPEX - Printer Extent Mode -> Screen */
					default:
						debug("ESC [ ? " + DCEvars[0] + " h, unsupported.");
						break;
					}
				}
				break;
			case 'i': // DEC Printer Control, autoprint, echo screenchars to
				// printer
				// This is different to CSI i!
				// Also: "Autoprint prints a final display line only when the
				// cursor is moved off the line by an autowrap or LF, FF, or
				// VT (otherwise do not print the line)."
				switch (DCEvars[0]) {
				case 1:
					if (debug > 1)
						debug("CSI ? 1 i : Print line containing cursor");
					break;
				case 4:
					if (debug > 1)
						debug("CSI ? 4 i : Start passthrough printing");
					break;
				case 5:
					if (debug > 1)
						debug("CSI ? 4 i : Stop passthrough printing");
					break;
				}
				break;
			case 'l': // DECRST
				/* DEC Mode reset */
				if (debug > 0)
					debug("ESC [ ? " + DCEvars[0] + " l");
				for (int i = 0; i <= DCEvar; i++) {
					switch (DCEvars[i]) {
					case 1: /* Application cursor keys */
						KeyUp[0] = "\u001b[A";
						KeyDown[0] = "\u001b[B";
						KeyRight[0] = "\u001b[C";
						KeyLeft[0] = "\u001b[D";
						break;
					case 2: /* DECANM */
						vt52mode = true;
						break;
					case 3: /* 80 columns */
						setScreenSize(80, height, true);
						break;
					case 6: /* DECOM: move outside margins. */
						moveoutsidemargins = true;
						break;
					case 7: /* DECAWM: Autowrap Mode OFF */
						wraparound = false;
						break;
					case 25: /* turn cursor off */
						showCursor(false);
						break;
					/* Unimplemented stuff: */
					/* 4 - scrolling mode, jump */
					/* 5 - dark background */
					/* 7 - DECAWM - no wrap around mode */
					/* 12 - local echo on */
					/* 18 - DECPFF - Printer Form Feed Mode -> Off */
					/* 19 - DECPEX - Printer Extent Mode -> Scrolling Region */
					case 9: /* X10 mouse */
					case 1000: /* xterm style mouse report OFF */
					case 1001:
					case 1002:
					case 1003:
						mouserpt = 0;
						break;
					default:
						debug("ESC [ ? " + DCEvars[0] + " l, unsupported.");
						break;
					}
				}
				break;
			case 'n':
				if (debug > 0)
					debug("ESC [ ? " + DCEvars[0] + " n");
				switch (DCEvars[0]) {
				case 15:
					/* printer? no printer. */
					write((ESC) + "[?13n", false);
					debug("ESC[5n");
					break;
				default:
					debug("ESC [ ? " + DCEvars[0] + " n, unsupported.");
					break;
				}
				break;
			default:
				debug("ESC [ ? " + DCEvars[0] + " " + c + ", unsupported.");
				break;
			}
			break;
		case TSTATE_CSI_EX:
			term_state = TSTATE_DATA;
			switch (c) {
			case ESC:
				term_state = TSTATE_ESC;
				break;
			default:
				debug("Unknown character ESC[! character is " + (int) c);
				break;
			}
			break;
		case TSTATE_CSI_TICKS:
			term_state = TSTATE_DATA;
			switch (c) {
			case 'p':
				debug("Conformance level: " + DCEvars[0] + " (unsupported),"
						+ DCEvars[1]);
				if (DCEvars[0] == 61) {
					output8bit = false;
					break;
				}
				if (DCEvars[1] == 1) {
					output8bit = false;
				} else {
					output8bit = true; /* 0 or 2 */
				}
				break;
			default:
				debug("Unknown ESC [...  \"" + c);
				break;
			}
			break;
		case TSTATE_CSI_EQUAL:
			term_state = TSTATE_DATA;
			switch (c) {
			case '0':
			case '1':
			case '2':
			case '3':
			case '4':
			case '5':
			case '6':
			case '7':
			case '8':
			case '9':
				DCEvars[DCEvar] = DCEvars[DCEvar] * 10 + (c) - 48;
				term_state = TSTATE_CSI_EQUAL;
				break;
			case ';':
				DCEvar++;
				DCEvars[DCEvar] = 0;
				term_state = TSTATE_CSI_EQUAL;
				break;

			case 'F': /* SCO ANSI foreground */
			{
				int newcolor;

				debug("ESC [ = " + DCEvars[0] + " F");

				attributes &= ~COLOR_FG;
				newcolor = ((DCEvars[0] & 1) << 2) | (DCEvars[0] & 2)
						| ((DCEvars[0] & 4) >> 2);
				attributes |= (newcolor + 1) << COLOR_FG_SHIFT;

				break;
			}
			case 'G': /* SCO ANSI background */
			{
				int newcolor;

				debug("ESC [ = " + DCEvars[0] + " G");

				attributes &= ~COLOR_BG;
				newcolor = ((DCEvars[0] & 1) << 2) | (DCEvars[0] & 2)
						| ((DCEvars[0] & 4) >> 2);
				attributes |= (newcolor + 1) << COLOR_BG_SHIFT;
				break;
			}

			default:
				debugStr.append("Unknown ESC [ = ");
				for (int i = 0; i <= DCEvar; i++) {
					debugStr.append(DCEvars[i]).append(',');
				}
				debugStr.append(c);
				debug(debugStr.toString());
				debugStr.setLength(0);
				break;
			}
			break;
		case TSTATE_CSI_DOLLAR:
			term_state = TSTATE_DATA;
			switch (c) {
			case '}':
				debug("Active Status Display now " + DCEvars[0]);
				statusmode = DCEvars[0];
				break;
			/*
			 * bad documentation? case '-':
			 * debug("Set Status Display now "+DCEvars[0]); break;
			 */
			case '~':
				debug("Status Line mode now " + DCEvars[0]);
				break;
			default:
				debug("UNKNOWN Status Display code " + c + ", with Pn="
						+ DCEvars[0]);
				break;
			}
			break;
		case TSTATE_CSI:
			term_state = TSTATE_DATA;
			switch (c) {
			case '"':
				term_state = TSTATE_CSI_TICKS;
				break;
			case '$':
				term_state = TSTATE_CSI_DOLLAR;
				break;
			case '=':
				term_state = TSTATE_CSI_EQUAL;
				break;
			case '!':
				term_state = TSTATE_CSI_EX;
				break;
			case '?':
				DCEvar = 0;
				DCEvars[0] = 0;
				term_state = TSTATE_DCEQ;
				break;
			case '0':
			case '1':
			case '2':
			case '3':
			case '4':
			case '5':
			case '6':
			case '7':
			case '8':
			case '9':
				DCEvars[DCEvar] = DCEvars[DCEvar] * 10 + (c) - 48;
				term_state = TSTATE_CSI;
				break;
			case ';':
				DCEvar++;
				DCEvars[DCEvar] = 0;
				term_state = TSTATE_CSI;
				break;
			case 'c':/* send primary device attributes */
				/* send (ESC[?61c) */

				String subcode = "";
				if (terminalID.equals("vt320"))
					subcode = "63;";
				if (terminalID.equals("vt220"))
					subcode = "62;";
				if (terminalID.equals("vt100"))
					subcode = "61;";
				write((ESC) + "[?" + subcode + "1;2c", false);
				if (debug > 1)
					debug("ESC [ " + DCEvars[0] + " c");
				break;
			case 'q':
				if (debug > 1)
					debug("ESC [ " + DCEvars[0] + " q");
				break;
			case 'g':
				/* used for tabsets */
				switch (DCEvars[0]) {
				case 3:/* clear them */
					Tabs = new byte[width];
					break;
				case 0:
					Tabs[C] = 0;
					break;
				}
				if (debug > 1)
					debug("ESC [ " + DCEvars[0] + " g");
				break;
			case 'h':
				switch (DCEvars[0]) {
				case 4:
					insertmode = 1;
					break;
				case 20:
					debug("Setting CRLF to TRUE");
					sendcrlf = true;
					break;
				default:
					debug("unsupported: ESC [ " + DCEvars[0] + " h");
					break;
				}
				if (debug > 1)
					debug("ESC [ " + DCEvars[0] + " h");
				break;
			case 'i': // Printer Controller mode.
				// "Transparent printing sends all output, except the CSI 4 i
				// termination string, to the printer and not the screen,
				// uses an 8-bit channel if no parity so NUL and DEL will be
				// seen by the printer and by the termination recognizer code,
				// and all translation and character set selections are
				// bypassed."
				switch (DCEvars[0]) {
				case 0:
					if (debug > 1)
						debug("CSI 0 i:  Print Screen, not implemented.");
					break;
				case 4:
					if (debug > 1)
						debug("CSI 4 i:  Enable Transparent Printing, not implemented.");
					break;
				case 5:
					if (debug > 1)
						debug("CSI 4/5 i:  Disable Transparent Printing, not implemented.");
					break;
				default:
					debug("ESC [ " + DCEvars[0] + " i, unimplemented!");
				}
				break;
			case 'l':
				switch (DCEvars[0]) {
				case 4:
					insertmode = 0;
					break;
				case 20:
					debug("Setting CRLF to FALSE");
					sendcrlf = false;
					break;
				default:
					debug("ESC [ " + DCEvars[0] + " l, unimplemented!");
					break;
				}
				break;
			case 'A': // CUU
			{
				int limit;
				/* FIXME: xterm only cares about 0 and topmargin */
				if (R >= getTopMargin()) {
					limit = getTopMargin();
				} else
					limit = 0;
				if (DCEvars[0] == 0)
					R--;
				else
					R -= DCEvars[0];
				if (R < limit)
					R = limit;
				if (debug > 1)
					debug("ESC [ " + DCEvars[0] + " A");
				break;
			}
			case 'B': // CUD
				/* cursor down n (1) times */
			{
				int limit;
				if (R <= getBottomMargin()) {
					limit = getBottomMargin();
				} else
					limit = rows - 1;
				if (DCEvars[0] == 0)
					R++;
				else
					R += DCEvars[0];
				if (R > limit)
					R = limit;
				else {
					if (debug > 2)
						debug("Not limited.");
				}
				if (debug > 2)
					debug("to: " + R);
				if (debug > 1)
					debug("ESC [ " + DCEvars[0] + " B (at C=" + C + ")");
				break;
			}
			case 'C':
				if (DCEvars[0] == 0)
					DCEvars[0] = 1;
				while (DCEvars[0]-- > 0) {
					C++;
				}
				if (C >= columns)
					C = columns - 1;
				if (debug > 1)
					debug("ESC [ " + DCEvars[0] + " C");
				break;
			case 'd': // CVA
				R = DCEvars[0];
				if (R < 0)
					R = 0;
				else if (R >= height)
					R = height - 1;
				if (debug > 1)
					debug("ESC [ " + DCEvars[0] + " d");
				break;
			case 'D':
				if (DCEvars[0] == 0)
					DCEvars[0] = 1;
				while (DCEvars[0]-- > 0) {
					C--;
				}
				if (C < 0)
					C = 0;
				if (debug > 1)
					debug("ESC [ " + DCEvars[0] + " D");
				break;
			case 'r': // DECSTBM
				if (DCEvar > 0) // Ray: Any argument is optional
				{
					R = DCEvars[1] - 1;
					if (R < 0)
						R = rows - 1;
					else if (R >= rows) {
						R = rows - 1;
					}
				} else
					R = rows - 1;
				int bot = R;
				if (R >= DCEvars[0]) {
					R = DCEvars[0] - 1;
					if (R < 0)
						R = 0;
				}
				setMargins(R, bot);
				_SetCursor(0, 0);
				if (debug > 1)
					debug("ESC [" + DCEvars[0] + " ; " + DCEvars[1] + " r");
				break;
			case 'G': /* CUP / cursor absolute column */
				C = DCEvars[0];
				if (C < 0)
					C = 0;
				else if (C >= width)
					C = width - 1;
				if (debug > 1)
					debug("ESC [ " + DCEvars[0] + " G");
				break;
			case 'H': /* CUP / cursor position */
				/* gets 2 arguments */
				_SetCursor(DCEvars[0] - 1, DCEvars[1] - 1);
				if (debug > 2) {
					debug("ESC [ " + DCEvars[0] + ";" + DCEvars[1]
							+ " H, moveoutsidemargins " + moveoutsidemargins);
					debug("	-> R now " + R + ", C now " + C);
				}
				break;
			case 'f': /* move cursor 2 */
				/* gets 2 arguments */
				R = DCEvars[0] - 1;
				C = DCEvars[1] - 1;
				if (C < 0)
					C = 0;
				else if (C >= width)
					C = width - 1;
				if (R < 0)
					R = 0;
				else if (R >= height)
					R = height - 1;
				if (debug > 2)
					debug("ESC [ " + DCEvars[0] + ";" + DCEvars[1] + " f");
				break;
			case 'S': /* ind aka 'scroll forward' */
				if (DCEvars[0] == 0)
					insertLine(rows - 1, SCROLL_UP);
				else
					insertLine(rows - 1, DCEvars[0], SCROLL_UP);
				break;
			case 'L':
				/* insert n lines */
				if (DCEvars[0] == 0)
					insertLine(R, SCROLL_DOWN);
				else
					insertLine(R, DCEvars[0], SCROLL_DOWN);
				if (debug > 1)
					debug("ESC [ " + DCEvars[0] + "" + (c) + " (at R " + R
							+ ")");
				break;
			case 'T': /* 'ri' aka scroll backward */
				if (DCEvars[0] == 0)
					insertLine(0, SCROLL_DOWN);
				else
					insertLine(0, DCEvars[0], SCROLL_DOWN);
				break;
			case 'M':
				if (debug > 1)
					debug("ESC [ " + DCEvars[0] + "" + (c) + " at R=" + R);
				if (DCEvars[0] == 0)
					deleteLine(R);
				else
					for (int i = 0; i < DCEvars[0]; i++)
						deleteLine(R);
				break;
			case 'K':
				if (debug > 1)
					debug("ESC [ " + DCEvars[0] + " K");
				/* clear in line */
				switch (DCEvars[0]) {
				case 6: /* 97801 uses ESC[6K for delete to end of line */
				case 0:/* clear to right */
					if (C < columns - 1)
						deleteArea(C, R, columns - C, 1, attributes);
					break;
				case 1:/* clear to the left, including this */
					if (C > 0)
						deleteArea(0, R, C + 1, 1, attributes);
					break;
				case 2:/* clear whole line */
					deleteArea(0, R, columns, 1, attributes);
					break;
				}
				break;
			case 'J':
				/* clear below current line */
				switch (DCEvars[0]) {
				case 0:
					if (R < rows - 1)
						deleteArea(0, R + 1, columns, rows - R - 1, attributes);
					if (C < columns - 1)
						deleteArea(C, R, columns - C, 1, attributes);
					break;
				case 1:
					if (R > 0)
						deleteArea(0, 0, columns, R, attributes);
					if (C > 0)
						deleteArea(0, R, C + 1, 1, attributes);// include up to
					// and including
					// current
					break;
				case 2:
					deleteArea(0, 0, columns, rows, attributes);
					break;
				}
				if (debug > 1)
					debug("ESC [ " + DCEvars[0] + " J");
				break;
			case '@':
				if (debug > 1)
					debug("ESC [ " + DCEvars[0] + " @");
				for (int i = 0; i < DCEvars[0]; i++)
					insertChar(C, R, ' ', attributes);
				break;
			case 'X': {
				int toerase = DCEvars[0];
				if (debug > 1)
					debug("ESC [ " + DCEvars[0] + " X, C=" + C + ",R=" + R);
				if (toerase == 0)
					toerase = 1;
				if (toerase + C > columns)
					toerase = columns - C;
				deleteArea(C, R, toerase, 1, attributes);
				// does not change cursor position
				break;
			}
			case 'P':
				if (debug > 1)
					debug("ESC [ " + DCEvars[0] + " P, C=" + C + ",R=" + R);
				if (DCEvars[0] == 0)
					DCEvars[0] = 1;
				for (int i = 0; i < DCEvars[0]; i++)
					deleteChar(C, R);
				break;
			case 'n':
				switch (DCEvars[0]) {
				case 5: /* malfunction? No malfunction. */
					writeSpecial((ESC) + "[0n");
					if (debug > 1)
						debug("ESC[5n");
					break;
				case 6:
					// DO NOT offset R and C by 1! (checked against
					// /usr/X11R6/bin/resize
					// FIXME check again.
					// FIXME: but vttest thinks different???
					writeSpecial((ESC) + "[" + R + ";" + C + "R");
					if (debug > 1)
						debug("ESC[6n");
					break;
				default:
					if (debug > 0)
						debug("ESC [ " + DCEvars[0] + " n??");
					break;
				}
				break;
			case 's': /* DECSC - save cursor */
				Sc = C;
				Sr = R;
				Sa = attributes;
				if (debug > 3)
					debug("ESC[s");
				break;
			case 'u': /* DECRC - restore cursor */
				C = Sc;
				R = Sr;
				attributes = Sa;
				if (debug > 3)
					debug("ESC[u");
				break;
			case 'm': /* attributes as color, bold , blink, */
				if (debug > 3)
					debug("ESC [ ");
				if (DCEvar == 0 && DCEvars[0] == 0)
					attributes = 0;
				for (int i = 0; i <= DCEvar; i++) {
					switch (DCEvars[i]) {
					case 0:
						if (DCEvar > 0) {
							if (terminalID.equals("scoansi")) {
								attributes &= COLOR; /*
													 * Keeps color. Strange but
													 * true.
													 */
							} else {
								attributes = 0;
							}
						}
						break;
					case 1:
						attributes |= BOLD;
						attributes &= ~LOW;
						break;
					case 2:
						/* SCO color hack mode */
						if (terminalID.equals("scoansi") && ((DCEvar - i) >= 2)) {
							int ncolor;
							attributes &= ~(COLOR | BOLD);

							ncolor = DCEvars[i + 1];
							if ((ncolor & 8) == 8)
								attributes |= BOLD;
							ncolor = ((ncolor & 1) << 2) | (ncolor & 2)
									| ((ncolor & 4) >> 2);
							attributes |= ((ncolor) + 1) << COLOR_FG_SHIFT;
							ncolor = DCEvars[i + 2];
							ncolor = ((ncolor & 1) << 2) | (ncolor & 2)
									| ((ncolor & 4) >> 2);
							attributes |= ((ncolor) + 1) << COLOR_BG_SHIFT;
							i += 2;
						} else {
							attributes |= LOW;
						}
						break;
					case 3: /* italics */
						attributes |= INVERT;
						break;
					case 4:
						attributes |= UNDERLINE;
						break;
					case 7:
						attributes |= INVERT;
						break;
					case 8:
						attributes |= INVISIBLE;
						break;
					case 5: /* blink on */
						break;
					/*
					 * 10 - ANSI X3.64-1979, select primary font, don't display
					 * control chars, don't set bit 8 on output
					 */
					case 10:
						gl = 0;
						usedcharsets = true;
						break;
					/*
					 * 11 - ANSI X3.64-1979, select second alt. font, display
					 * control chars, set bit 8 on output
					 */
					case 11: /* SMACS , as */
					case 12:
						gl = 1;
						usedcharsets = true;
						break;
					case 21: /* normal intensity */
						attributes &= ~(LOW | BOLD);
						break;
					case 23: /* italics off */
						attributes &= ~INVERT;
						break;
					case 25: /* blinking off */
						break;
					case 27:
						attributes &= ~INVERT;
						break;
					case 28:
						attributes &= ~INVISIBLE;
						break;
					case 24:
						attributes &= ~UNDERLINE;
						break;
					case 22:
						attributes &= ~BOLD;
						break;
					case 30:
					case 31:
					case 32:
					case 33:
					case 34:
					case 35:
					case 36:
					case 37:
						attributes &= ~COLOR_FG;
						attributes |= ((DCEvars[i] - 30) + 1) << COLOR_FG_SHIFT;
						break;
					case 38:
						if (DCEvars[i + 1] == 5) {
							attributes &= ~COLOR_FG;
							attributes |= ((DCEvars[i + 2]) + 1) << COLOR_FG_SHIFT;
							i += 2;
						}
						break;
					case 39:
						attributes &= ~COLOR_FG;
						break;
					case 40:
					case 41:
					case 42:
					case 43:
					case 44:
					case 45:
					case 46:
					case 47:
						attributes &= ~COLOR_BG;
						attributes |= ((DCEvars[i] - 40) + 1) << COLOR_BG_SHIFT;
						break;
					case 48:
						if (DCEvars[i + 1] == 5) {
							attributes &= ~COLOR_BG;
							attributes |= (DCEvars[i + 2] + 1) << COLOR_BG_SHIFT;
							i += 2;
						}
						break;
					case 49:
						attributes &= ~COLOR_BG;
						break;
					case 90:
					case 91:
					case 92:
					case 93:
					case 94:
					case 95:
					case 96:
					case 97:
						attributes &= ~COLOR_FG;
						attributes |= ((DCEvars[i] - 82) + 1) << COLOR_FG_SHIFT;
						break;
					case 100:
					case 101:
					case 102:
					case 103:
					case 104:
					case 105:
					case 106:
					case 107:
						attributes &= ~COLOR_BG;
						attributes |= ((DCEvars[i] - 92) + 1) << COLOR_BG_SHIFT;
						break;

					default:
						debugStr.append("ESC [ ").append(DCEvars[i]).append(
								" m unknown...");
						debug(debugStr.toString());
						debugStr.setLength(0);
						break;
					}
					if (debug > 3) {
						debugStr.append(DCEvars[i]).append(';');
						debug(debugStr.toString());
						debugStr.setLength(0);
					}
				}
				if (debug > 3) {
					debugStr.append(" (attributes = ").append(attributes)
							.append(")m");
					debug(debugStr.toString());
					debugStr.setLength(0);
				}
				break;
			default:
				debugStr.append("ESC [ unknown letter: ").append(c)
						.append(" (").append((int) c).append(')');
				debug(debugStr.toString());
				debugStr.setLength(0);
				break;
			}
			break;
		case TSTATE_TITLE:
			switch (c) {
			case ESC:
				term_state = TSTATE_ESC;
				break;
			default:
				// TODO save title
				break;
			}
			break;
		default:
			term_state = TSTATE_DATA;
			break;
		}

		setCursorPosition(C, R);
	}

	/* hard reset the terminal */
	public void reset() {
		gx[0] = 'B';
		gx[1] = 'B';
		gx[2] = 'B';
		gx[3] = 'B';

		gl = 0; // default GL to G0
		gr = 2; // default GR to G2

		onegl = -1; // Single shift override

		/* reset tabs */
		int nw = width;
		if (nw < 132)
			nw = 132;
		Tabs = new byte[nw];
		for (int i = 0; i < nw; i += 8) {
			Tabs[i] = 1;
		}

		deleteArea(0, 0, width, height, attributes);
		setMargins(0, height);
		C = R = 0;
		_SetCursor(0, 0);

		if (display != null)
			display.resetColors();

		showCursor(true);
		/* FIXME: */
		term_state = TSTATE_DATA;
	}
}
